package com.laf.manage.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import com.arcsoft.face.FaceFeature;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.enums.ImageFormat;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.laf.common.core.domain.OCRResult;
import com.laf.common.core.domain.model.LoginMobileUser;
import com.laf.common.core.redis.RedisCache;
import com.laf.common.exception.ServiceException;
import com.laf.common.utils.FaceUtils;
import com.laf.common.utils.IdOCRUtils;
import com.laf.common.utils.SecurityUtils;
import com.laf.common.utils.StringUtils;
import com.laf.common.utils.uuid.IdUtils;
import com.laf.manage.constant.CommonConstant;
import com.laf.manage.domain.po.AuthInfo;
import com.laf.manage.mapper.mp.AuthInfoMapper;
import com.laf.manage.service.IAuthInfoService;
import lombok.RequiredArgsConstructor;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 用户认证管理Service业务层处理
 *
 * @author zian
 * @date 2024-11-01
 */
@Service
@RequiredArgsConstructor
public class AuthInfoServiceImpl extends ServiceImpl<AuthInfoMapper, AuthInfo> implements IAuthInfoService {

    private final AuthInfoMapper authInfoMapper;

    private final RedisCache redisCache;

    private final FaceUtils faceUtils;

    private final FileStorageService fileStorageService;

    private final IdOCRUtils idOCRUtils;

    /**
     * 查询用户认证管理列表
     *
     * @param authInfo 用户认证管理
     * @return 用户认证管理
     */
    @Override
    public List<AuthInfo> selectAuthInfoList(AuthInfo authInfo) {
        List<AuthInfo> authInfoList = authInfoMapper.selectList(buildQueryWrapper(authInfo));
        return authInfoList;
    }


    private LambdaQueryWrapper<AuthInfo> buildQueryWrapper(AuthInfo query) {
        LocalDateTime beginTime = query.getBeginTime();
        LocalDateTime endTime = query.getEndTime();
        LambdaQueryWrapper<AuthInfo> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(query.getRealName()), AuthInfo::getRealName, query.getRealName());
        lqw.between(beginTime != null && endTime != null, AuthInfo::getCreateTime, beginTime, endTime);
        lqw.orderByDesc(AuthInfo::getCreateTime);
        return lqw;
    }


    /**
     * 上传身份证图片
     *
     * @param fileBack
     * @param fileFront
     * @return
     */
    @Override
    public String uploadIDCardImg(MultipartFile fileBack, MultipartFile fileFront) {
        try {
            OCRResult ocrResult = idOCRUtils.identityCardUtilsOCR(fileBack, fileFront);
            String uuid = IdUtils.simpleUUID();
            redisCache.setCacheObject(CommonConstant.ID_CARD + uuid, ocrResult, 30, TimeUnit.MINUTES);
            return uuid;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 上传人脸图片进行认证
     *
     * @param faceFile uuid 和 人脸图片
     */
    @Override
    public String uploadFaceImg(MultipartFile faceFile, String uuid) {
        // 1. 检查缓存中是否对应的数据
        String key = CommonConstant.ID_CARD + uuid;
        OCRResult ocrResult = (OCRResult) redisCache.getCacheObject(key);
        if (ocrResult == null) throw new ServiceException("请先上传身份证图片");

        // 2. 获取上传的人脸特征
        FaceFeature targetFeature = getTargetFeature(faceFile);

        // 3. 获取缓存中身份信息以及人脸信息
        String base64 = ocrResult.getBase64();
        if (StrUtil.isBlank(base64)) throw new ServiceException("请重新上传身份证图片");
        byte[] sourceBytes = Base64.decode(base64);
        FaceFeature sourceFeature = new FaceFeature(sourceBytes);

        // 4. 拿着缓存中特征和上传的人脸特征进行对比
        boolean compare = faceUtils.compare(targetFeature, sourceFeature);
        if (!compare) throw new ServiceException("不是同一个人");

        // 5. 认证成功重新缓存信息
        String result = IdUtils.simpleUUID();
        ocrResult.setBase64(null);
        redisCache.setCacheObject(CommonConstant.FACE_INFO + uuid, ocrResult, 30, TimeUnit.MINUTES);

        // 6. 删除原来的缓存
        redisCache.deleteObject(key);

        return result;
    }

    /**
     * 对上传人脸进行特征提取
     *
     * @param faceFile 上传的人脸图片
     * @return 人脸特征
     */
    private FaceFeature getTargetFeature(MultipartFile faceFile) {
        ImageInfo targetImageInfo = null;
        try {
            // TODO 耗时操作
            targetImageInfo = ImageFactory.getRGBData(faceFile.getInputStream());
        } catch (IOException e) {
            throw new ServiceException("无法解析人脸图片");
        }


        byte[] imageData = targetImageInfo.getImageData();
        Integer width = targetImageInfo.getWidth();
        Integer height = targetImageInfo.getHeight();
        ImageFormat imageFormat = targetImageInfo.getImageFormat();

        ArrayList<FaceInfo> faceInfos = faceUtils.detectFaces(imageData, width, height, imageFormat);
        if (faceInfos.isEmpty()) {
            throw new ServiceException("未检测到人脸图片，请重新上传");
        }

        return faceUtils.extractFaceFeature(imageData, width, height, imageFormat, faceInfos.get(0));
    }

    /**
     * 添加认证记录
     *
     * @param fileFront 身份证背面图片 和 uuid
     */
    @Override
    public void addAuth(MultipartFile fileFront, String uuid) {
        LoginMobileUser userInfo = SecurityUtils.getMobileLoginUser();
        AuthInfo one = lambdaQuery().eq(AuthInfo::getUserId, userInfo.getUserId()).one();
        if (one != null) throw new ServiceException("已添加认证记录,无需重复添加");

        // 1. 获取缓存中认证信息
        String key = CommonConstant.FACE_INFO + uuid;
        Serializable cacheObject = redisCache.getCacheObject(key);
        if (cacheObject == null) {
            throw new ServiceException("请上传人脸图片完成认证");
        }
        OCRResult ocrResult = (OCRResult) cacheObject;

        // 2. 上传图片
        String objectName = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd/")) + "face/";
        FileInfo fileInfo = fileStorageService.of(fileFront)
                .setPath(objectName)
                .upload();

        // 3. 封装po
        AuthInfo info = new AuthInfo();
        info.setUserId(userInfo.getUserId());
        info.setRealName(ocrResult.getName());
        info.setIdNumber(ocrResult.getIdNumber());
        info.setIdNumberUrl(fileInfo.getUrl());
        info.setExpiryDate(ocrResult.getExpiryDate());
        String userName = userInfo.getUsername();
        info.setCreateBy(userName);
        info.setUpdateBy(userName);


        // 4.删除当前用户原来的凭证记录
        remove(
                Wrappers.<AuthInfo>lambdaQuery()
                        .eq(AuthInfo::getUserId, userInfo.getUserId())
        );

        // 5. 保存认证记录
        boolean b = save(info);
        if (!b) throw new ServiceException("保存认证记录失败");

        // 删除缓存
        redisCache.deleteObject(key);
    }

    /**
     * 检测是否已经认证
     *
     * @return true 已认证 false 未认证
     */
    @Override
    public Boolean checkAuthStatus() {
        Long userId = SecurityUtils.getMobileUserId();
        return lambdaQuery().eq(AuthInfo::getUserId, userId).count() > 0;
    }

    /**
     * 删除凭证
     * @param id
     */
    @Override
    public void deleteAuthById(Long id) {
        AuthInfo authInfo = getById(id);
        if (authInfo == null) throw new ServiceException("认证记录不存在");
        boolean b = removeById(id);
        if (!b) throw new ServiceException("删除认证记录失败");
    }

    /**
     * 根据id查询认证记录
     * @param id
     * @return
     */
    @Override
    public AuthInfo getAuthById(Long id) {
        AuthInfo authInfo = getById(id);
        if (authInfo == null) throw new ServiceException("认证记录不存在");
        return authInfo;
    }

    /**
     * 判断用户存在认证记录
     * @param userId 用户id
     * @return
     */
    @Override
    public boolean isAuthByUserId(Long userId) {
        Long count = lambdaQuery()
                .eq(AuthInfo::getUserId, userId)
                .count();
        return count > 0;
    }


}
